/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ru.niir.goodfil.ui;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import ru.niir.goodfil.audit.domain.UserUIEvent;
import ru.niir.goodfil.audit.domain.enums.Crud;
import ru.niir.goodfil.audit.util.Assert;
import ru.niir.goodfil.db.Client;
import ru.niir.goodfil.db.Role;
import ru.niir.goodfil.db.User;
import ru.niir.goodfil.exceptions.CreateFailureException;
import ru.niir.goodfil.exceptions.RemoveFailureException;
import ru.niir.goodfil.exceptions.ValidationException;
import ru.niir.goodfil.ui.utils.PageIndex;
import ru.niir.goodfil.utils.filters.UsersFilter;
import ru.niir.goodfil.utils.validation.ValidationError;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static ru.niir.goodfil.utils.Utils._assert;

/**
 * Бин управления пользователями.
 *
 * @author Носов А.В.
 */
public class UsersBean extends PageBean {

    private static Logger logger = Logger.getLogger(UsersBean.class);

    /**
     * Название настройки, задающей размер страницы
     */
    private static final String USERS_PAGE_SIZE_KEY = ".users.page.size";
    /**
     * Название настройки, задающей отступ от текущей страницы влево для пэйджинатора
     */
    private static final String USERS_LEFT_PAGE_OFFSET_SIZE = ".users.page.left.offset";
    /**
     * Название настройки, задающей отступ от текущей страницы вправо для пэйджинатора
     */
    private static final String USERS_RIGHT_PAGE_OFFSET_SIZE = ".users.page.right.offset";

    /**
     * Количество страниц (с учетом фильтра)
     */
    private int pagesCount = -1;
    /**
     * Количество замещенных менеджеров (с учетом фильтра)
     */
    private int usersCount = -1;
    /**
     * Размер страницы
     */
    private int pageSize = -1;
    /**
     * Отступ от текущей страницы влево для паджинатора (кол-во страниц, слева от текущей)
     */
    private int _leftPageOffset = -1;
    /**
     * Отступ от текущей страницы вправо для паджинатора (кол-во страниц, справа от текущей)
     */
    private int _rightPageOffset = -1;

    /**
     * Пользователь
     */
    private User user = new User();
    /**
     * email пользователя
     */
    private String userMail;
    /**
     * Текст для отправки на email пользователя
     */
    private String userMailText;
    /**
     * Идентификатор пользователя
     */
    private long userId = -1;
    /**
     * Проверка email пользователя
     */
    public static final Pattern email = Pattern.compile("^[-a-z0-9!#$%&'*+/=?^_`{|}~]+(?:\\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*@(?:[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?\\.)*(?:aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|[a-z][a-z])$");

    public UsersBean() {
//        if (!getSecurityBean().getIsRoleAdministrator())
//            throw new GoodfilSecurityException("Только администраторы имеют доступ к управлению пользователями системы");
    }

    /**
     * Возвращает (согласно настройкам) размер страницы (для пэйджинатора).
     *
     * @return размер страници
     */
    public int getPageSize() {
        gf().createIfNotExists(USERS_PAGE_SIZE_KEY, 10);
        if (pageSize == -1) pageSize = gf().getInt(USERS_PAGE_SIZE_KEY);
        return pageSize;
    }

    /**
     * Возвращает (согласно настройкам) размер отступа влево (в страницах) для пэйджинатора.
     *
     * @return размер отступа
     */
    public int getLeftPaggeOffset() {
        gf().createIfNotExists(USERS_LEFT_PAGE_OFFSET_SIZE, 5);
        if (_leftPageOffset == -1) _leftPageOffset = gf().getInt(USERS_LEFT_PAGE_OFFSET_SIZE);
        return _leftPageOffset;
    }

    /**
     * Возарщает (согласно настройкам) размер отступа вправо (в страницах) для пэйджинатора
     *
     * @return размер отступа
     */
    public int getRightPaggeOffset() {
        gf().createIfNotExists(USERS_RIGHT_PAGE_OFFSET_SIZE, 5);
        if (_rightPageOffset == -1) _rightPageOffset = gf().getInt(USERS_RIGHT_PAGE_OFFSET_SIZE);
        return _rightPageOffset;
    }

    /**
     * Возвращает фильтр пользователей.
     *
     * @return фильтр пользователей
     */
    private UsersFilter getUsersFilter() {
        return getSessionBean().getUsersFilter();
    }

    /**
     * Возвращает количество пользователей (с учетом фильтра).
     *
     * @return количество пользователей
     */
    public int getUsersCount() {
        //  UNSAFE: Небезопасное приведение к int
        if (usersCount == -1) usersCount = (int) gf().getUsersCount(getUsersFilter());
        return usersCount;
    }

    /**
     * Возвращает количество страниц (с учетом фильтра).
     *
     * @return количество страниц
     */
    public int getPagesCount() {
        if (pagesCount == -1) pagesCount = getUsersCount() / getPageSize() + 1;
        return pagesCount;
    }

    /**
     * Перейти к началу (на первую страницу)
     */
    public void toBegin() {
        applyPage();
        setPage(1);
    }

    /**
     * Перейти к концу (на последнюю страницу)
     */
    public void toEnd() {
        applyPage();
        setPage(getPagesCount());
    }

    /**
     * Отступить на несколько страниц назад
     */
    public void longPrevPage() {
        applyPage();
        int p = getPage() - getLeftPaggeOffset() - 1;
        if (p < 1) p = 1;
        setPage(p);
    }

    /**
     * Отступить на несколько страниц вперед
     */
    public void longNextPage() {
        applyPage();
        int p = getPage() + getRightPaggeOffset() + 1;
        if (p > getPagesCount()) p = getPagesCount();
        setPage(p);
    }

    /**
     * Перейти на страницу вперед
     */
    public void nextPage() {
        applyPage();
        if (getPage() < getPagesCount()) setPage(getPage() + 1);
    }

    /**
     * Перейти на страницу назад
     */
    public void prevPage() {
        applyPage();
        if (getPage() > 1) setPage(getPage() - 1);
    }

    /**
     * Получить номер текущей страницы
     */
    public int getPage() {
        return getSessionBean().getUsersPage();
    }

    /**
     * Устанавливает номер текущей страницы.
     *
     * @param page номер страницы
     */
    public void setPage(int page) {
        getSessionBean().setUsersPage(page);
    }

    /**
     * Применить текущую страницу
     */
    public void applyPage() {
        usersCount = -1;
        pagesCount = -1;
    }

    /**
     * Возвращает номера страниц для отрисовки в пэйджинаторе.
     *
     * @return номера страниц
     */
    public List<PageIndex> getPageIndexes() {
        int minPage = getPage() - getLeftPaggeOffset();
        if (minPage < 1) minPage = 1;

        int maxPage = minPage + getLeftPaggeOffset() + getRightPaggeOffset();
        if (maxPage > getPagesCount()) maxPage = getPagesCount();

        List<PageIndex> result = new ArrayList<PageIndex>();
        for (int i = minPage; i <= maxPage; i++)
            result.add(new PageIndex(i, i == getPage()));

        return result;
    }

    /**
     * Применить фильтр
     */
    public void applyFilter() {
        setPage(1);
        usersCount = -1;
        pagesCount = -1;
    }

    /**
     * Возвращает номер первой страницы, которую необходимо отрисовать в пэйджинаторе.
     *
     * @return номер первой страници
     */
    private int getPageStart() {
        int pageStart = getPage() * getPageSize() - getPageSize();
        if (pageStart < 0) pageStart = 0;
        return pageStart;
    }

    /**
     * Возвращает номер последней страницы, которую необходимо отрисовать в пэйджинаторе.
     *
     * @return номер последней страницы
     */
    private int getPageEnd() {
        int pageEnd = getPageStart() + getPageSize();
        if (pageEnd > getUsersCount()) pageEnd = getUsersCount();
        return pageEnd;
    }

    /**
     * Возвращает пользователя.
     *
     * @return пользователь
     */
    public User getUser() {
        if (user.getId() != userId) {
            if (userId == -1) user = new User();
            else {
                user = gf().getUserById(userId);
                if (user.getClientId() != null && user.getClientId() != -1)
                    user.setClient(gf().getClientById(user.getClientId()));
            }
        }
        return user;
    }

    /**
     * Устанавливает пользователя.
     *
     * @param user пользователь
     */
    public void setUser(User user) {
        this.user = user;
    }

    /**
     * Возвращает email пользователя.
     *
     * @return email
     */
    public String getUserMail() {
        return userMail;
    }

    /**
     * Устанавливает email пользователя.
     *
     * @param userMail email
     */
    public void setUserMail(String userMail) {
        this.userMail = userMail;
    }

    /**
     * Возвращает такст для отправки пользователю на email.
     *
     * @return текст
     */
    public String getUserMailText() {
        return userMailText;
    }

    /**
     * Устананавливает текст для отправки пользователю на email.
     *
     * @param userMailText текст
     */
    public void setUserMailText(String userMailText) {
        this.userMailText = userMailText;
    }

    /**
     * Возвращает идентификатор пользователя.
     *
     * @return идентификатор пользователя
     */
    public long getUserId() {
        return userId;
    }

    /**
     * Устанавливает идентифифкатор пользователя.
     *
     * @param userId идентификатор
     */
    public void setUserId(long userId) {
        this.userId = userId;
    }

    /**
     * Возвращает список пользователей.
     *
     * @return список пользователей
     */
    public List<User> getUsers() {
        //  Получаем перечень ролей пользователей
        List<Role> roles = Role.listRoles();

        //  Получаем перечень пользователей
        List<User> users = gf().getUsers(getPageStart(), getPageSize(), getUsersFilter());

        for (User u : users) {
            //  Назначаем пользователю роль
            for (Role role : roles)
                if (role.getId() == u.getUserRoleId())
                    u.setRole(role);

            //  Назначаем пользователю начальника
            if (u.getParentManagerId() != null)
                u.setParentManger(gf().getUserById(u.getParentManagerId()));

            //  Назначаем клиента
            if (u.getIsClient() && u.getClientId() != null) {
                Client client = gf().getClientById(u.getClientId());
                if (client != null) u.setClient(client);
            }
        }
        return users;
    }

    /**
     * Возвращает список менеджеров.
     *
     * @return список менеджеров
     */
    public List<User> getManagers() {
        return gf().getManagers();
    }

    /**
     * Возвращает список установленных менеджеров.
     *
     * @return список менеджеров
     */
    public List<SelectItem> getManagersAsSelectItems() {

        //  EXPERIMENTAL: Возвращаем только менеджеров, связанных с даннм

        List<SelectItem> result = new ArrayList<SelectItem>() {{
            add(new SelectItem(-1, "Не установлен"));
        }};
        for (User manager : getManagers()) {
            _assert(manager != null);
            if (manager == null) continue;

            //  Для администратора добавляем в список всех менеджеров
            if (getSecurityBean().getIsRoleAdministrator())
                result.add(new SelectItem(manager.getId(), manager.getFio()));

            //  Для менеджера в списке должен присутсовать он сам и его подчиненные 1го уровня
            if (getSecurityBean().getIsRoleManager() &&
                    (manager.getId() == getSecurityBean().getUserId() ||
                            (manager.getParentManagerId() != null) && (manager.getParentManagerId() == getSecurityBean().getUserId())))
                result.add(new SelectItem(manager.getId(), manager.getFio()));
        }
        return result;
    }

    /**
     * Возвращает список менеджеров блять каких то там нахуй.
     *
     * @return список менеджеров
     */
    public List<SelectItem> getManagersWithEmptyAsSelectItems() {
        List<SelectItem> result = getManagersAsSelectItems();
        result.add(1, new SelectItem(0, "Отсутствует"));
        return result;
    }

    /**
     * Возвращает список ролей.
     *
     * @return список ролей
     */
    public List<SelectItem> getRolesAsSelectItems() {
        List<SelectItem> result = new ArrayList<SelectItem>();
        result.add(new SelectItem(-1, "Не указана"));
        for (Role role : Role.listRoles())
            result.add(new SelectItem(role.getId(), role.getName()));
        return result;
    }

    /**
     * Если флаг выставлен в true, пароль будет отправлен на эл.почту, указанную в карточке клиента.
     * Если нет - то пароль просто будет изменен, но никаких уведомлений отправлено не будет.
     */
    private boolean sendByEmail;

    /**
     * Если флаг выставлен в true, пароль будет отправлен на эл.почту, указанную в карточке клиента.
     * Если нет - то пароль просто будет изменен, но никаких уведомлений отправлено не будет.
     */
    public boolean isSendByEmail() {
        return sendByEmail;
    }

    /**
     * Если флаг выставлен в true, пароль будет отправлен на эл.почту, указанную в карточке клиента.
     * Если нет - то пароль просто будет изменен, но никаких уведомлений отправлено не будет.
     */
    public void setSendByEmail(boolean sendByEmail) {
        this.sendByEmail = sendByEmail;
    }

    /**
     * Выслать пароль для пользователя
     */
    public void sendPassword() {
        try {
            Assert.notNull(user);
            Assert.notNull(user.getId());

            gf().sendPasswordForUser(user.getId(), sendByEmail);

            if (sendByEmail) {
                facesInfo("Пароль был выслан пользователю");
            } else {
                facesInfo("Для пользователя был сгенерирован новый пароль");
            }
        } catch (Exception e) {
            logger.error(e);
            e.printStackTrace();

            facesError("Произошла ошибка, установить новый пароль не удалось");
        }
    }

    /**
     * Отправить текст пользователю на почтовый ящик.
     */
    public void sendTextMail() {
        if (StringUtils.isEmpty(userMail)) facesError("Не указан почтовый ящик");
        if (StringUtils.isEmpty(userMailText)) facesError("Не указан текст письма");

        if (hasFacesErrors()) return;

        try {
            gf().sendEmailMsg(userMail, userMailText);

            facesInfo("Сообщение отправлено");
        } catch (Exception e) {
            logger.error(e);
            e.printStackTrace();

            facesError("Не удалось отправить сообщение");
        }
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Работа с БД----------
     * -------------------------------------------------------------------------
     */

    /**
     * Удалить пользователя.
     */
    public void removeUser() {
        if (user.getId() == 1)
            facesError("Удаление пользователя \"Администратор\" не допускается");

        if (getSessionBean().getGoodfil().removeCheckUser(user.getId()).isError())
            facesError("Невозможно удалить пользователя. Либо с ним связаны клиенты, либо у него есть подчиненные");

        if (hasFacesErrors()) return;

        try {
            UserUIEvent event = prepageUserEvent(Crud.DELETE);
            getSessionBean().getGoodfil().removeUser(user.getId());
            getAuditService().logUserUIEvent(event);
        } catch (RemoveFailureException ex) {
            logger.error(ex);
            ex.printStackTrace();
            String[] objs = ex.getRemoveCheckResult().getLinkedObjects();
            for (String obj : objs) {
                facesError("Пользователя нельзя удалить, т.к. он связан с другими объектами.");
                break;
            }
        }
    }

    private UserUIEvent prepageUserEvent(Crud crud) {
        User theUser = gf().getUserById(this.user.getId());

        String name = null;
        if (theUser.getIsManager()) {
            name = theUser.getFio();
        } else {
            Client client = theUser.getClient();
            if (client == null) {
                client = gf().getClientById(theUser.getClientId());
            }
            name = client.getCompanyName();
        }

        String role = null;
        if (theUser.getRole() != null) {
            role = theUser.getRole().getName();
        } else {
            role = Role.asString(theUser.getUserRoleId());
        }

        UserUIEvent userUIEvent = new UserUIEvent();

        userUIEvent.setIp(getAuditHelper().getIp());
        userUIEvent.setLogin(getAuditHelper().getLogin());

        userUIEvent.setCrud(crud);
        userUIEvent.setUserId(String.valueOf(theUser.getId()));
        userUIEvent.setUserLogin(theUser.getLogin());
        userUIEvent.setName(String.valueOf(name));
        userUIEvent.setRole(String.valueOf(role));
        userUIEvent.setMail(theUser.getEmail());
        userUIEvent.setTabNumber(theUser.getNum());
        userUIEvent.setCreationDate(theUser.getCreationDateAsString());

        return userUIEvent;
    }

    /**
     * Сохранить пользователя.
     */
    public void saveUser() {
        try {
            validateUser(user);

            if (hasFacesErrors()) return;

            if (user.getId() == -1) {

                try {
                    gf().saveUser(user);
                    getAuditService().logUserUIEvent(prepageUserEvent(Crud.CREATE));
                    facesInfo("Данные о пользователе обновлены");
                } catch (CreateFailureException cfe) {
                    facesError("Невозможно создать пользователя: " + cfe.getResult().getMsg());
                } catch (ValidationException ve) {
                    for (ValidationError error : ve.getResult().getErrors())
                        facesError(error.toHumanString());
                }
                facesInfo("Пользователь сохранен");
            } else {
                try {
                    gf().updateUser(user);
                    getAuditService().logUserUIEvent(prepageUserEvent(Crud.UPDARE));
                    facesInfo("Данные о пользователе обновлены");
                } catch (ValidationException ve) {
                    for (ValidationError error : ve.getResult().getErrors())
                        facesError(error.toHumanString());
                }
                facesInfo("Пользователь замещен");
            }

        } catch (Exception e) {
            logger.error(e);
            e.printStackTrace();

            facesError("Не удалось сохранить данные пользователя");
        }
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Валидаторы----------
     * -------------------------------------------------------------------------
     */

    /**
     * Проверка пользователя.
     *
     * @param user пользователь
     */
    private void validateUser(User user) {
        user.setUserRoleId(getSelectedUserRoleId());

        if (user.getId() == 1) {
            facesError("Данные Администратора редактировать не допускается");
            return;
        }

        if (user.getUserRoleId() == -1) {
            facesError("Не указана роль пользователя");
            return;
        }

        if (StringUtils.isEmpty(user.getLogin())) facesError("Необходимо указать логин пользователя");

        if (user.getUserRoleId() == Role.MANAGER || user.getUserRoleId() == Role.ADMIN || user.getUserRoleId() == Role.STOREMAN) {
            if (StringUtils.isEmpty(user.getFio())) facesError("ФИО пользователя должно быть заполнено");
            if (StringUtils.isEmpty(user.getEmail())) facesError("Необходмио указать адрес эл. почты пользователя");
            if (StringUtils.isEmpty(user.getNum())) facesError("Табельный номер пользователя должен быть заполнен");
        }

        if (user.getUserRoleId() == Role.CLIENT) {
            if (user.getClientId() == -1) facesError("Необходимо выбрать клиента");
        }


        //  TODO: Проверка почтового ящика
        //  TODO: Проверка табельного номера
    }

    /**
     * Проверка поля e-mail.
     *
     * @param context
     * @param component
     * @param obj
     */
    public void validateEmail(FacesContext context, UIComponent component, Object obj) {
        String clientE_Mail = obj.toString();
        if (!clientE_Mail.equals("")) {
            Matcher matcher = email.matcher(clientE_Mail);
            if (!matcher.matches()) facesError("Поле Email заполнено не верно.");
        }
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Что то для чего то----------
     * -------------------------------------------------------------------------
     */

    public boolean getIsUserRoleIdSelected() {
        return getSelectedUserRoleId() != -1;
    }

    public int getSelectedUserRoleId() {
        if (!getSessionMap().containsKey("selectedUserRoleId"))
            getSessionMap().put("selectedUserRoleId", -1);

        return Integer.parseInt(getSessionMap().get("selectedUserRoleId").toString());
    }

    public void setSelectedUserRoleId(int selectedUserRoleId) {
        getSessionMap().put("selectedUserRoleId", selectedUserRoleId);
    }

    public boolean getSelectedUserRoleIsAdministrator() {
        return getSelectedUserRoleId() == Role.ADMIN;
    }

    public boolean getSelectedUserRoleIsManager() {
        return getSelectedUserRoleId() == Role.MANAGER;
    }

    public boolean getSelectedUserRoleIsClient() {
        return getSelectedUserRoleId() == Role.CLIENT;
    }

    public boolean getSelectedUserRoleIsStoreman() {
        return getSelectedUserRoleId() == Role.STOREMAN;
    }

    public String getSelectedUserRoleString() {
        if (getSelectedUserRoleIsAdministrator()) return "Администратор";
        if (getSelectedUserRoleIsManager()) return "Менеджер";
        if (getSelectedUserRoleIsStoreman()) return "Кладовщик";
        if (getSelectedUserRoleIsClient()) return "Клиент";
        return "";
    }


}